package mil.af.amc.adpetracker.service;

import mil.af.amc.adpetracker.dao.*;
import mil.af.amc.adpetracker.exceptions.DuplicateUsernameException;
import mil.af.amc.adpetracker.exceptions.EquipmentItemNotFoundException;
import mil.af.amc.adpetracker.exceptions.InvalidPasswordException;
import mil.af.amc.adpetracker.exceptions.UserNotFoundException;
import mil.af.amc.adpetracker.model.*;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.mail.MailException;
import org.springframework.mail.MailSender;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import org.springframework.security.providers.encoding.MessageDigestPasswordEncoder;
import org.springframework.security.providers.encoding.PasswordEncoder;

import java.util.*;

/**
 * Implementation of User Service
 *
 * //TODO: this class doesn't need to implement UserService anymore... all login stuff is done in AccountService
 *
 * @author TSgt Ferguson
 */
@Service("userService")
public class UserServiceImpl implements UserService {

    private static final Log log = LogFactory.getLog(UserServiceImpl.class);

    private UserDao userDao;

    @Autowired
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    private AccountDao accountDao;

    @Autowired
    public void setAccountDao(AccountDao accountDao) {
        this.accountDao = accountDao;
    }

    private AdpeAccountDao adpeAccountDao;

    @Autowired
    public void setAdpeAccountDao(AdpeAccountDao adpeAccountDao) {
        this.adpeAccountDao = adpeAccountDao;
    }

    private EquipmentItemDao equipmentItemDao;

    @Autowired
    public void setEquipmentItemDao(EquipmentItemDao equipmentItemDao) {
        this.equipmentItemDao = equipmentItemDao;
    }

    private MaintenanceEntryDao maintenanceDao;

    @Autowired
    public void setMaintenanceDao(MaintenanceEntryDao maintenanceDao) {
        this.maintenanceDao = maintenanceDao;
    }

    private OrganizationDao organizationDao;

    @Autowired
    public void setOrganizationDao(OrganizationDao organizationDao) {
        this.organizationDao = organizationDao;
    }

    private PasswordEncoder passwordEncoder;

    public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
        this.passwordEncoder = passwordEncoder;
    }

    private MailSender mailSender;

    @Autowired
    public void setMailSender(MailSender mailSender) {
        this.mailSender = mailSender;
    }

    public User login(String username, String password) throws UserNotFoundException, InvalidPasswordException {

        throw new UnsupportedOperationException();

    }

    public User register(Map registerMap) throws DuplicateUsernameException {

        throw new UnsupportedOperationException();

    }

    @SuppressWarnings({"ForLoopReplaceableByForEach"})
    public void assignEquipmentToUser(Map dataMap) throws UserNotFoundException, EquipmentItemNotFoundException {

        int userId = (Integer) dataMap.get("userId");

        User user = userDao.getById(userId);
        if (user == null) {
            throw new UserNotFoundException("User not found");
        }

        Integer[] itemId = (Integer[]) dataMap.get("itemId");

        List<EquipmentItem> itemList = new ArrayList<EquipmentItem>();
        for (int i = 0; i < itemId.length; i++) {
            EquipmentItem item = equipmentItemDao.getById(itemId[i]);
            if (item == null) {
                throw new EquipmentItemNotFoundException("Equipment Item Not Found");
            }

            // Add to the user side
            if (user.getAssignedEquipment().contains(item) == false) {
                user.getAssignedEquipment().add(item);
                userDao.save(user);

                // Add to the equipment side
                item.setAssignedUser(user);
                equipmentItemDao.save(item);

                MaintenanceEntry entry = new MaintenanceEntry();
                entry.setEntryDate(new Date());
                entry.setItem(item);
                entry.setEntryText("Item assigned to user " + user.getDisplayName());

                maintenanceDao.save(entry);

                itemList.add(item);
            }

        }

        StringBuilder sb = new StringBuilder();
        sb.append("If this is difficult to read, please click on \"Restore Line Breaks\".\r\n\r\n");
        sb.append("You have had the following ADPE items assigned to you: \r\n\r\n");

        for (Iterator<EquipmentItem> it = itemList.iterator(); it.hasNext();) {
            EquipmentItem item = it.next();
            sb.append(item.getDescription());
            sb.append("(");
            sb.append(item.getItemModel().getManufacturer().getName());
            sb.append(" ");
            sb.append(item.getItemModel().getName());
            sb.append(", ");
            sb.append("Serial Number: ");
            sb.append(item.getSerial());
            sb.append(")\r\n");
        }

        sb.append("\r\n");

        sb.append("If you do not have this item, please contact your equipment custodian.");
        sb.append("\r\n");
        sb.append("\r\n");
        sb.append("The following list shows all items currently assigned to you: \r\n\r\n");
        sb.append("Serial Number        Manufacturer      Model       Description         Type\r\n");
        sb.append("-----------------------------------------------------------------------------------\r\n");
        List<EquipmentItem> userFullList = user.getAssignedEquipment();
        for (Iterator<EquipmentItem> it = userFullList.iterator(); it.hasNext();) {

            EquipmentItem item = it.next();
            sb.append(item.getSerial());
            sb.append("         ");
            sb.append(item.getItemModel().getManufacturer().getName());
            sb.append("         ");
            sb.append(item.getItemModel().getName());
            sb.append("         ");
            sb.append(item.getDescription());
            sb.append("         ");
            sb.append(item.getItemModel().getType().getName());
            sb.append("\r\n");
        }
        try {
            SimpleMailMessage msg = new SimpleMailMessage();
            msg.setTo(user.getEmail());
            msg.setFrom("ADPETRACK-NOREPLY@scott.af.mil");
            msg.setSubject("Assignment of Equipment");
            msg.setText(sb.toString());

            mailSender.send(msg);
        } catch (Exception e) {
            log.debug("Unable to send email message to user.");
        }
    }

    public User getUserById(int userId) {

        return userDao.getById(userId);

    }

    public User getUserByUsername(String username) {

        return userDao.getByUsername(username);
    }

    public User inprocessUser(Map userMap) throws ServiceException {

        String firstName = (String) userMap.get("firstName");
        String lastName = (String) userMap.get("lastName");
        String commPhone = (String) userMap.get("commPhone");
        String dsnPhone = (String) userMap.get("dsnPhone");
        String email = (String) userMap.get("email");
        int organizationId = (Integer) userMap.get("organizationId");

        if (email == null) {
            throw new ServiceException("messages.validation.register.invalid-email", "You did not provide an email address.");
        }
        User user = new User();
        user.setFirstName(firstName);
        user.setLastName(lastName);
        user.setComm(commPhone);
        user.setDsn(dsnPhone);
        user.setEmail(email);

        Organization org = organizationDao.getById(organizationId);
        user.setAssignedOrganization(org);

        int userId = userDao.add(user);
        user.setId(userId);

        org.getAssignedUsers().add(user);

        try {
            String message = "Welcome to the 375 CSPTS Software Engineering flight ADPE tracking system. You have been" +
                    "successfully inprocessed into the system, and should soon be getting email messages regarding equipment" +
                    "items assigned to you. If these messages refer to items that are not already in your possession, " +
                    "please contact your ADPE Custodian.";
            SimpleMailMessage msg = new SimpleMailMessage();
            msg.setTo(user.getEmail());
            msg.setFrom("ADPETRACK-NOREPLY@scott.af.mil");
            msg.setSubject("Welcome to the 375 CSPTS ADPE Tracking System");
            mailSender.send(msg);
        } catch (MailException e) {

        }

        return user;
    }

    public void outprocessUser(int userId) throws ServiceException {

        User user = userDao.getById(userId);
        if (user == null) {
            throw new ServiceException("error.user.notfound", "User #" + userId + " not found");
        }

        outprocessUser(user);
    }

    public void outprocessUser(User user) throws ServiceException {

        if (user.getAssignedEquipment().size() > 0) {
            throw new ServiceException("error.outprocess.user.equipmentstillassigned",
                    "Cannot outprocess user with equipment still assigned");
        }

        userDao.delete(user);
    }

    public List<User> getAll() {

        return userDao.getAll();
    }

    public List<AdpeAccount> getUserAdpeAccounts(int userId) {

        User user = userDao.getById(userId);
        return getUserAdpeAccounts(user);

    }

    public List<AdpeAccount> getUserAdpeAccounts(User user) {
        return adpeAccountDao.getAccountsByUser(user);
    }

    public void editUser(Map userMap) {

        User user = userDao.getById((Integer) userMap.get("userId"));

        user.setLastName((String) userMap.get("lastName"));
        user.setFirstName((String) userMap.get("firstName"));
        user.setComm((String) userMap.get("commPhone"));
        user.setDsn((String) userMap.get("dsnPhone"));
        user.setEmail((String) userMap.get("email"));

        int orgId = (Integer) userMap.get("organizationId");
        Organization org = organizationDao.getById(orgId);
        user.setAssignedOrganization(org);

        userDao.save(user);

    }


    public List<User> getAllByOrganization(int organizationId) {

        Organization o = organizationDao.getById(organizationId);
        return getAllByOrganization(o);

    }

    protected List<User> getAllByOrganization(Organization o) {

        return userDao.getAllByOrganization(o);

    }

    public List<User> getUsersWithNoAssignedEquipment(int organizationId) {

        Organization o = organizationDao.getById(organizationId);
        return getUsersWithNoAssignedEquipment(o);

    }

    protected List<User> getUsersWithNoAssignedEquipment(Organization o) {

        return userDao.getUsersWithNoAssignedEquipment(o);

    }

    public List<User> getUsersWithNoAssignedCpu(int organizationId) {

        Organization o = organizationDao.getById(organizationId);
        return getUsersWithNoAssignedCpu(o);

    }

    protected List<User> getUsersWithNoAssignedCpu(Organization o) {

        return userDao.getUsersWithNoCpu(o);

    }

    public static String randomString(int length) {

        Random randGen = new Random();

        char[] numbersAndLetters = ("0123456789abcdefghijklmnopqrstuvwxyz" +
                "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ").toCharArray();

        if (length < 1) {
            return null;
        }
        // Create a char buffer to put random letters and numbers in.
        char [] randBuffer = new char[length];
        for (int i = 0; i < randBuffer.length; i++) {
            randBuffer[i] = numbersAndLetters[randGen.nextInt(71)];
        }
        return new String(randBuffer);
    }

    public void resetPassword(String email) throws ServiceException {

        User user = userDao.getByUsername(email);
        String oldPassword = user.getAccount().getPassword();

        if (user == null) {
            log.debug("Failed attempt to retrieve a user with email address: " + email);
            throw new ServiceException("messages.user.notfound", "Unable to locate user with given email address");
        }
        if (user.getAccount() == null) {

            throw new ServiceException("messages.user.noaccount", "User does not have an account in the system");
        }

        String randomPassword = UserServiceImpl.randomString(8);

        // sha-256 it. Use Spring Security's encoder to generate the hashed password since it will be used to authenticate
        // against it later
        MessageDigestPasswordEncoder encoder = new MessageDigestPasswordEncoder("SHA-256");
        encoder.setEncodeHashAsBase64(true);
        String hashedPassword = encoder.encodePassword(randomPassword, email);
        user.getAccount().setPassword(hashedPassword);

        userDao.save(user);

        // Mail the original to the user
        StringBuilder msg = new StringBuilder();
        msg.append("You have requested that your password be reset in the ADPE Tracking application.\r\n\r\n");
        msg.append("Your new password is: ").append(randomPassword);

        try {
            SimpleMailMessage mail = new SimpleMailMessage();
            mail.setFrom("ADPETRACK-NOREPLY@scott.af.mil");
            mail.setTo(user.getEmail());
            mail.setSubject("ADPE TRACKING SYSTEM Password Reset");
            mail.setText(msg.toString());
            mailSender.send(mail);
        } catch (MailException e) {

            // Egads! We can't send the message to tell the user their new password, so roll it back!
            user.getAccount().setPassword(oldPassword);
            userDao.save(user);

            throw new ServiceException("messages.userpassword.email.error", "Unable to send password reset email!");
        }

    }

}
